Практикум 4. Расчет атомных орбиталей водорода

In [3]:
import numpy as np
import scipy.special
import scipy.misc
import numpy2cube # Автор: Андрей Демкив

Зададим волновую фунцию

In [25]:
def w(n,l,m,d):
    
    x,y,z = np.mgrid[-d:d:30j,-d:d:30j,-d:d:30j]  # Зададим сетку из 30 точек от -d до d
    
    # Переход от декартовых к сферическим координатам
    r = lambda x,y,z: np.sqrt(x**2+y**2+z**2)  # расстояние от начала координат
    theta = lambda x,y,z: np.arccos(z/r(x,y,z))  # зенитный угол
    phi = lambda x,y,z: np.arctan(y/x)  # азимутальный угол
    
    a0 = 1
    
    R = lambda r,n,l: (2*r/n/a0)**l * np.exp(-r/n/a0) * \
        scipy.special.genlaguerre(n-l-1,2*l+1)(2*r/n/a0)  # Радиальная компонента волновой функции
    WF = lambda r,theta,phi,n,l,m: R(r,n,l) * \
        scipy.special.sph_harm(m,l,phi,theta)  # Волновая функция (радиальная часть * угловую)
    absWF = lambda r,theta,phi,n,l,m: np.absolute(WF(r,theta,phi,n,l,m))**2  #  Квадрат модуля волновой функции
 
    return WF(r(x,y,z),theta(x,y,z),phi(x,y,z),n,l,m)

Посчитаем орбитали для первых трех главных квантовых чисел

In [24]:
# Зададим цикл по перебору квантовых чисел

for n in range(1,4):
    for l in range(0,n):
        for m in range(-l,l+1,1):
            d = 10 * n
            step=2*d/(30-1)
            grid=w(n, l, m, d)
            name='%s-%s-%s' % (n,l,m)
            # для сохранения нужно задать координаты старта grid и шаг по каждому направлению
            numpy2cube.npy2cube(grid, (-d,)*3, (step,)*3, name+'.cube')

Нарисуем их в паймоле

In [22]:
import __main__
__main__.pymol_argv = [ 'pymol', '-x' ]

import pymol
pymol.finish_launching()
from pymol import cmd,stored
In [23]:
cmd.reinitialize()
#ramp is defined as: [x1, rgb1, alpha1, ...]
cmd.volume_ramp_new('my_ramp', [\
    -0.8,   1.00, 0.00, 0.00, 0.12,
    -0.01,  1.00, 0.75, 0.00, 0.88,
    -0.005, 1.00, 0.00, 0.00, 0.00,
     0, 1,1,1,0.0,
     0.005, 0.00, 0.00, 1.00, 0.00, 
     0.01,  0.00, 0.75, 1.00, 0.88, 
     0.8,   0.00, 0.00, 1.00, 0.12])

import time
clip_vals = [0.001,
             -25, 0, -25, 0,
             -37, 0, -35, 0, 0, 0, 0.001, 0, 0]
clip_i = 0
for n in range(1,4):
    for l in range(0,n):
        for m in range(-l,l+1,1):
            name='%s-%s-%s' % (n,l,m)
            cmd.hide("volume", "all")
            cmd.load(f"{name}.cube")
            cmd.volume(f"{name}_volume", name)
            cmd.volume_color(f"{name}_volume", "my_ramp")
            cmd.clip("near", clip_vals[clip_i])
            if clip_vals[clip_i] == 0:
                cmd.set_view ([\
     0.999755025,    0.011685935,    0.018848220,\
    -0.011722559,    0.999928892,    0.001830711,\
    -0.018824987,   -0.002051400,    0.999820948,\
     0.000000000,    0.000000000, -242.648696899,\
     0.000000000,    0.000000000,    0.000000000,\
   -48.560935974,  533.858337402,  -20.000000000])
            else:
                # Чтобы красиво видеть pz и dz^2 орбитали
                cmd.set_view([\
    -0.018926797,   -0.164738640,   -0.986155510,\
    -0.008280928,    0.986323476,   -0.164607957,\
     0.999786615,    0.005050731,   -0.020031987,\
     0.000000000,    0.000000000, -181.888442993,\
     0.000000000,    0.000000000,    0.000000000,\
   181.388442993,  182.388442993,  -20.000000000])
            cmd.draw()
            time.sleep(2)
            cmd.png(f"{name}.png")
            cmd.clip("near", -clip_vals[clip_i])
            clip_i += 1

Посмотрим на них. Из-за разных set_view масштаб у нас разный. Он один и тот же между группой s, pz и dz^2 орбиталей, а также между всеми остальными орбиталями. В ряде случаев орбитали "срезаны", чтобы увидеть плотность в плоскости экрана, близкой к ядру.

In [8]:
from IPython.display import display,Image
for n in range(1,4):
    for l in range(0,n):
        for m in range(-l,l+1,1):
            display(Image(f"{n}-{l}-{m}.png"))

Сравним с тем, что рисует нам ORCA (2s-2p орбитали)

In [22]:
file = open("h.inp", "w")
orca_inp = \
"""! UHF SVP XYZFile
%plots
Format CUBE
MO("H1.cube",1,0);
MO("H2.cube",2,0);
MO("H3.cube",3,0);
MO("H4.cube",4,0);
end
*xyz 0 2
H 0 0 0
*"""
file.write(orca_inp)
file.close()
In [13]:
cmd.reinitialize()
cmd.volume_ramp_new('my_ramp', [\
    -0.8,   1.00, 0.00, 0.00, 0.12,
    -0.01,  1.00, 0.75, 0.00, 0.88,
    -0.005, 1.00, 0.00, 0.00, 0.00,
     0, 1,1,1,0.0,
     0.005, 0.00, 0.00, 1.00, 0.00, 
     0.01,  0.00, 0.75, 1.00, 0.88, 
     0.8,   0.00, 0.00, 1.00, 0.12])
for i in range(1, 5):
    name=f"H-{i}"
    cmd.hide("volume", "all")
    cmd.load(f"{name}.cube")
    cmd.volume(f"{name}_volume", name)
    cmd.volume_color(f"{name}_volume", "my_ramp")
    time.sleep(2)
    cmd.png(f"{name}.png")
    display(Image(f"{name}.png"))

Видим отличие в 2p орбиталях. Отличие, вероятно, объясняется тем, что наш способ расчета является "честным", а ORCA по умолчанию немного хитрит и считает не 2px, 2py, а (2px + 2py)/2, (2px - 2py)/2j вместо них, так называемые "real orbitals". Почитать про них можно, например, в параграфе 13.7.2 книжки Molecular Modelling for Beginners 2nd edition.
Несмотря на то, что мы рисуем вроде как "честные" орбитали, при визуализации в cube файл попадает только вещественная часть волновой функции. Этим объясняется "странный" вид орбиталей с l>0 и |m|>0, ведь они имеют ненулевую комлексную компоненту. Правильная визуализация с помощью программы hydrogen приводится ниже в конце практикума, но ее в виде cube-файла не сохранить, так как для каждого узла, в котором мы сохраняем значение комплексной функции, надо сохранить не одно число, а 2: модуль (или квадрат модуля) и фазу волновой функции.
Примечательно, что ORCA посчитала нам только орбитали 0-4 (мы при этом нарисовали только 2s и 2p, всего 4 штуки, а не 5). Думаю, это объясняется тем, что в базисе SVP для атома водорода всего 5 базисных функций: большая s, малая s и 3 поляризационных p. Соответственно, из них можно родить 2 орбитали s-типа и 3 орбитали p-типа. Вот и получили наши 5 функций.
Возьмем базис намного побольше (aug-cc-pV5Z), чтобы получить орбитали энергией повыше, и посмотрим, что ORCA нам нарисует. Нарисовали орбитали 2s-4s (14 штук).

In [25]:
cmd.reinitialize()
cmd.volume_ramp_new('my_ramp', [\
    -0.8,   1.00, 0.00, 0.00, 0.12,
    -0.01,  1.00, 0.75, 0.00, 0.88,
    -0.005, 1.00, 0.00, 0.00, 0.00,
     0, 1,1,1,0.0,
     0.005, 0.00, 0.00, 1.00, 0.00, 
     0.01,  0.00, 0.75, 1.00, 0.88, 
     0.8,   0.00, 0.00, 1.00, 0.12])
for i in range(1, 15):
    name=f"H{i}"
    cmd.hide("volume", "all")
    cmd.load(f"aug-cc-pV5Z/{name}.cube")
    cmd.volume(f"{name}_volume", name)
    cmd.volume_color(f"{name}_volume", "my_ramp")
    time.sleep(2)
    cmd.png(f"aug-cc-pV5Z/{name}.png")
    display(Image(f"{name}.png"))

Здесь снова видим отличие в p-орбиталях. С увеличением базиса, появились и орбитали с энергией повыше (d). d-орбитали тоже отличаются от тех, что нарисовали мы. Все они похожи на dz^2. Думаю, объясняется это из тех же соображений, что и в случае p-орбиталей: имеем дело с real orbitals.

Помимо книжки выше, можно на википедии почитать про real orbitals. То же самое. Еще там гифка красивая есть.

Real_orbitals

Еще одна картинка с википедии. https://upload.wikimedia.org/wikipedia/commons/2/2a/Atomic_orbitals_spdf_m-eigenstates_and_superpositions.png
В левой части рисунка вещественные орбитали, в правой - честные решения уравнения Шредингера, цветом обозначена комплексная фаза волновой функции

img